{
 "cells": [
  {
   "cell_type": "markdown",
   "id": "e411edd1",
   "metadata": {},
   "source": [
    "# `ipydatagrid`: fast, performant data grid\n",
    "\n",
    "## https://github.com/bloomberg/ipydatagrid\n",
    "\n",
    "\n",
    "`ipydatagrid` is a complete data grid solution providing full integration with pandas DataFrames without compromising on performance:\n",
    "\n",
    "- built-in sorting and filtering\n",
    "- full integration with the jupyter-widgets ecosystem\n",
    "- highly customisable renderers\n",
    "- complete two-way data binding between Python and the front-end\n",
    "- supports Vega expressions for conditional formatting and styling\n",
    "\n",
    "**Installation**:\n",
    "```bash\n",
    "conda install -c conda-forge ipydatagrid\n",
    "```"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "99d62ba4",
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipydatagrid import DataGrid, TextRenderer, BarRenderer, Expr, VegaExpr\n",
    "import pandas as pd\n",
    "import numpy as np\n",
    "import requests"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7effabda",
   "metadata": {},
   "source": [
    "#### Use any dataset"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5b17db11",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Numpy/pandas\n",
    "np.random.seed(0)\n",
    "p_t, n = 100, 260\n",
    "stock_df = pd.DataFrame(\n",
    "    {f'Stock {i}': p_t + np.round(np.random.standard_normal(n).cumsum(), 2) for i in range(10)}\n",
    ")\n",
    "\n",
    "# Requests/JSON\n",
    "req = requests.get(\"https://raw.githubusercontent.com/bloomberg/ipydatagrid/main/examples/cars.json\")\n",
    "data = req.json()\n",
    "cars_data = data['data']\n",
    "\n",
    "\n",
    "# Random matrix\n",
    "rand_df = pd.DataFrame(\n",
    "    {f'Column {col}': np.round(np.random.random(100), 2) for col in [chr(n) for n in range(65, 91)]}\n",
    ")\n",
    "\n",
    "# Small grid\n",
    "small_df = pd.DataFrame(\n",
    "    np.eye(10),\n",
    "    columns=[f'Col {i}' for i in range(10)],\n",
    "    index=[f'Row {i}' for i in range(10)]\n",
    ")\n",
    "\n",
    "# Multi-index\n",
    "top_level = ['Value Factors', 'Value Factors', 'Momentum Factors', 'Momentum Factors']\n",
    "bottom_level = ['Factor A', 'Factor B', 'Factor C', 'Factor D']\n",
    "\n",
    "nested_df = pd.DataFrame(np.random.randn(4,4).round(2),\n",
    "                         columns=pd.MultiIndex.from_arrays([top_level, bottom_level]),\n",
    "                         index=pd.Index(['Security {}'.format(x) for x in ['A', 'B', 'C', 'D']], name='Ticker'))"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6a080a60",
   "metadata": {},
   "source": [
    "#### Convert your pandas DataFrame to a datagrid - it 'just works'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5ea1f3b3",
   "metadata": {},
   "outputs": [],
   "source": [
    "df = pd.DataFrame(cars_data).set_index('index')\n",
    "df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "59bb027b",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = DataGrid(df)\n",
    "grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3cb90f0e",
   "metadata": {},
   "source": [
    "Additional constructor options"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "208d292a",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid = DataGrid(\n",
    "    dataframe=df,\n",
    "    base_row_size=30,\n",
    "    base_column_size=92,\n",
    "    base_row_header_size=128,\n",
    "    base_column_header_size=40\n",
    ")\n",
    "grid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cad00f6",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.header_visibility = 'row'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a22284d2",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.header_visibility = 'column'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "87ff5a6b",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.header_visibility = 'none'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6026e3af",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.header_visibility = 'all'"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3fd2765d",
   "metadata": {},
   "source": [
    "#### Style your data grid with custom renderers based on Vega expressions\n",
    "\n",
    "- Custom cell renderers can be defined for the entire grid or column-wise.\n",
    "\n",
    "- Two types of cell renderers are currently available: `TextRenderer` and `BarRenderer`.\n",
    "\n",
    "- Most of the TextRenderer/BarRenderer attributes (`background_color`, `text_color` etc.) can either be a `value`, a `bqplot` scale or a `VegaExpr` or `Expr` instance.\n",
    "\n",
    "The `VegaExpr` class allows you to define an attribute value as a result of a Vega-expression (see https://vega.github.io/vega/docs/expressions/):\n",
    "```python\n",
    "background_color = VegaExpr(\"value < 150 ? 'red' : 'green'\").\n",
    "```\n",
    "You can look at the vega-expression documentation for more information about available constants and functions. In the scope of the expression are also available: value: cell value, x and y: cell position in pixel, width and height of the cell, row and column: cell position."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ba9acf11",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import LinearScale, ColorScale, OrdinalColorScale, OrdinalScale\n",
    "from py2vega.functions.color import rgb\n",
    "\n",
    "def horsepower_coloring(cell):\n",
    "    if cell.value < 100:\n",
    "        return \"red\"\n",
    "    elif cell.value < 150:\n",
    "        return \"orange\"\n",
    "    else:\n",
    "        return \"green\"\n",
    "\n",
    "\n",
    "def weight_coloring(cell):\n",
    "    scaled_value = 1 if cell.value > 4500 else cell.value / 4500\n",
    "    color_value = scaled_value * 255\n",
    "\n",
    "    return rgb(color_value, 0, 0)\n",
    "\n",
    "\n",
    "renderers = {\n",
    "    \"Acceleration\": BarRenderer(\n",
    "        horizontal_alignment=\"center\",\n",
    "        bar_color=ColorScale(min=0, max=20, scheme=\"viridis\"),\n",
    "        bar_value=LinearScale(min=0, max=20),\n",
    "    ),\n",
    "    \"Cylinders\": TextRenderer(\n",
    "        background_color=Expr('\"grey\" if cell.row % 2 else default_value')\n",
    "    ),\n",
    "    \"Displacement\": TextRenderer(\n",
    "        text_color=ColorScale(min=97, max=455),\n",
    "        font=Expr(\n",
    "            \"'16px sans-serif' if cell.value > 400 else '12px sans-serif'\"\n",
    "        ),\n",
    "    ),\n",
    "    \"Horsepower\": TextRenderer(\n",
    "        text_color=\"black\", background_color=Expr(horsepower_coloring)\n",
    "    ),\n",
    "    \"Miles_per_Gallon\": TextRenderer(\n",
    "        background_color=Expr('\"grey\" if cell.value is None else default_value')\n",
    "    ),\n",
    "    \"Name\": TextRenderer(\n",
    "        background_color=Expr(\n",
    "            'rgb(0, 100, 255) if \"chevrolet\" in cell.value or \"ford\" in cell.value else default_value'\n",
    "        )\n",
    "    ),\n",
    "    \"Origin\": TextRenderer(\n",
    "        text_color=\"black\",\n",
    "        background_color=OrdinalColorScale(domain=[\"USA\", \"Japan\", \"Europe\"]),\n",
    "        horizontal_alignment=Expr(\n",
    "            \"'right' if cell.value in ['USA', 'Japan'] else 'left'\"\n",
    "        ),\n",
    "    ),\n",
    "    \"Weight_in_lbs\": TextRenderer(\n",
    "        text_color=\"black\", background_color=Expr(weight_coloring)\n",
    "    ),\n",
    "    \"Year\": TextRenderer(text_color=\"black\", background_color=\"green\"),\n",
    "}\n",
    "\n",
    "grid.renderers = renderers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d817fc52",
   "metadata": {},
   "outputs": [],
   "source": [
    "renderers[\"Name\"] \\\n",
    "    .background_color.value = '\"green\" if \"pontiac\" in cell.value or \"citroen\" in cell.value else default_value'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "da2f9483",
   "metadata": {},
   "outputs": [],
   "source": [
    "renderers[\"Year\"].background_color = \"yellow\""
   ]
  },
  {
   "cell_type": "markdown",
   "id": "105c1c08",
   "metadata": {},
   "source": [
    "#### Apply styling to column and row headers"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b9fdfb1d",
   "metadata": {},
   "outputs": [],
   "source": [
    "grid.header_renderer = TextRenderer(\n",
    "    background_color=Expr('\"salmon\" if cell.value == \"Horsepower\" else \"skyblue\"'),\n",
    "    font='italic small-caps bold 12px/30px Georgia, serif'\n",
    ")\n",
    "\n",
    "renderers['index'] = TextRenderer(background_color='slateblue')\n",
    "grid.renderers = renderers"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6b29748b",
   "metadata": {},
   "source": [
    "#### Built-in sorting and filtering functionality which you can trigger from both Python and directly via the GUI."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ca7acc77",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Filtering based on car origin and sorting based on displacement\n",
    "grid.transform(\n",
    "    [\n",
    "        {\n",
    "            \"type\": \"filter\",\n",
    "            \"operator\": \"=\",\n",
    "            \"columnIndex\": 2,\n",
    "            \"value\": \"Europe\",\n",
    "        },\n",
    "        {\"type\": \"sort\", \"columnIndex\": 9, \"desc\": True},\n",
    "    ]\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d0ba629b",
   "metadata": {},
   "source": [
    "#### Conditional formatting based on another cell"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f367e391",
   "metadata": {},
   "outputs": [],
   "source": [
    "def format_based_on_other_column(cell):\n",
    "    return (\"green\" if cell.column == 2 and cell.metadata.data[\"Return\"] > 0 else \"red\")\n",
    "\n",
    "signal_column_formatting = TextRenderer(\n",
    "    text_color=\"white\",\n",
    "    background_color=Expr(format_based_on_other_column),\n",
    ")\n",
    "\n",
    "renderers = {\n",
    "    \"Signal\": signal_column_formatting,\n",
    "    \"Return\": TextRenderer(background_color='seashell',\n",
    "                           text_color=VegaExpr('cell.value > 0 ? \"green\" : \"firebrick\"')\n",
    "    )\n",
    "}\n",
    "\n",
    "conditional_grid = DataGrid(\n",
    "    pd.DataFrame(\n",
    "        {\"Stock\": \"A B C D\".split(), \n",
    "         \"Return\": [0.11, -0.05, 0.08, -0.20], \n",
    "         \"Signal\": [\"Buy\", \"Sell\", \"Buy\", \"Sell\"]}\n",
    "    ),\n",
    "    column_widths={\"Stock\": 64, \"Return\": 64,  \"Signal\": 300},\n",
    "    base_row_size=30,\n",
    "    renderers=renderers,\n",
    "    layout={\"height\": \"150px\"},\n",
    ")\n",
    "\n",
    "conditional_grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "6d3a3e4e",
   "metadata": {},
   "source": [
    "#### Multi-index and nested columns DataFrames are also supported"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "73437da4",
   "metadata": {},
   "outputs": [],
   "source": [
    "nested_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e0ae031e",
   "metadata": {},
   "outputs": [],
   "source": [
    "columns_renderer = TextRenderer(\n",
    "    background_color='dimgray',\n",
    "    horizontal_alignment='center')\n",
    "\n",
    "renderers= {\n",
    "    \"('Ticker', '')\": TextRenderer(background_color='dimgray')\n",
    "}\n",
    "\n",
    "default_renderer = TextRenderer(\n",
    "    background_color=VegaExpr('cell.value > 0 ? \"steelblue\" : \"seagreen\"')\n",
    ")\n",
    "\n",
    "nested_grid = DataGrid(nested_df,\n",
    "                       base_column_size=90,\n",
    "                       column_widths={\"('Ticker', '')\": 80},\n",
    "                       layout={'height':'140px'},\n",
    "                       renderers=renderers,\n",
    "                       default_renderer=default_renderer,\n",
    "                       header_renderer=columns_renderer)\n",
    "\n",
    "nested_grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "410df8b0",
   "metadata": {},
   "source": [
    "#### Two-way selections model at your disposal\n",
    "\n",
    "DataGrid cells can be selected using mouse by simply clicking and dragging over the cells. Pressing Cmd / Ctrl key during selection will add to existing selections. Pressing Shift key allows selection of regions between two clicks.\n",
    "\n",
    "DataGrid supports three modes of selection `cell`, `row`, `column`. In order to disable selections, selection mode can be set to `none` which is the default setting.\n",
    "\n",
    "Selection Modes:\n",
    "\n",
    "- `cell`: Clicking on grid will select only the cell under mouse cursor\n",
    "- `row`: Clicking on grid will select all the cells on the row under mouse cursor\n",
    "- `column`: Clicking on grid will select all the cells on the column under mouse cursor\n",
    "\n",
    "You can clear all selections by hitting the `esc` key."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac98c8aa",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid = DataGrid(stock_df, selection_mode='cell')\n",
    "sel_grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "94def677",
   "metadata": {},
   "source": [
    "Select from the UI and retrieve on the Python side"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79431075",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid.selections"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d72f1cd3",
   "metadata": {},
   "source": [
    "We can also access the selected cell values"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d12be625",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid.selected_cell_values"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8eecef2a",
   "metadata": {},
   "source": [
    "Select from the Python side and see selections highlighted on the UI.\n",
    "\n",
    "Parameters:\n",
    "\n",
    "- `row1`: start row (starts from 0).\n",
    "- `row2`: end row (starts from 0).\n",
    "- `column1`: start column.\n",
    "- `column2`: end column.\n",
    "\n",
    "We can automatically clear any existing selections by passing a value for `clear_mode`:\n",
    "\n",
    "- `current`: clear last selection\n",
    "- `all`: clear all existing selections\n",
    "- `none`: do not clear selections (default)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "8ffc92b5",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Select top left corner of grid\n",
    "sel_grid.select(row1=0, column1=0, row2=1, column2=1, clear_mode='current')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "5274c236",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Clear selection\n",
    "sel_grid.clear_selection()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "8d187a96",
   "metadata": {},
   "source": [
    "We can select individual sells by omitting `row2` and `column2`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e528c1eb",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid.select(10, 5)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fb4fd2ff",
   "metadata": {},
   "source": [
    "When working with large grids, we can opt to use the `selected_cell_iterator`. It will yield values for each loop iteration, avoiding the need to store all selections in a list, in advance."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7e2fc113",
   "metadata": {},
   "outputs": [],
   "source": [
    "for cell in sel_grid.selected_cell_iterator:\n",
    "    print(f'Cell value: {cell}')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4aa08dfe",
   "metadata": {},
   "source": [
    "We can modify selections in place by passing a list of selections"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c26015ae",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid.selections = [\n",
    "    {\"r1\": 22, \"r2\": 20, \"c1\": 0, \"c2\": 2},\n",
    "    {\"r1\": 6, \"r2\": 6, \"c1\": 2, \"c2\": 2},\n",
    "    {\"r1\": 10, \"r2\": 10, \"c1\": 3, \"c2\": 3},\n",
    "    {\"r1\": 13, \"r2\": 13, \"c1\": 2, \"c2\": 2},\n",
    "]"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ccd1c933",
   "metadata": {},
   "source": [
    "Row selection mode"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c4b2ee47",
   "metadata": {},
   "outputs": [],
   "source": [
    "sel_grid.selection_mode='row'"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ac94cee4",
   "metadata": {},
   "outputs": [],
   "source": [
    "for i in range(260):\n",
    "    sel_grid.select(i, i) if i % 2 == 0 else None"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "fac64513",
   "metadata": {},
   "source": [
    "#### Two-way cell editing is possible\n",
    "\n",
    "Just pass `editable=True` to the grid's constructorand you're good to go (grids are not editable by default)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9e2f0158",
   "metadata": {},
   "outputs": [],
   "source": [
    "small_grid = DataGrid(small_df, \n",
    "                      editable=True, \n",
    "                      default_renderer=TextRenderer(\n",
    "                          background_color=VegaExpr(\"cell.value === 1 ? 'limegreen' : 'hotpink'\")\n",
    "                      ),\n",
    "                      layout={'height': '250px'})\n",
    "small_grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c578673c",
   "metadata": {},
   "source": [
    "You can change values directly in the UI by double clicking a cell and changing the value. You can navigate the grid using the keyboard. You can use the arrow keys or the grid's __cursor__:\n",
    "\n",
    "- __Down__: Enter\n",
    "- __Up__: Shift + Enter\n",
    "- __Right__: Tab\n",
    "- __Left__: Shift + Tab"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "823d35da",
   "metadata": {},
   "source": [
    "..or you can change it directly from the Python side"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7132c903",
   "metadata": {},
   "outputs": [],
   "source": [
    "# Returns a boolean to indicate whether operations was successful\n",
    "small_grid.set_cell_value('Col 0', 'Row 9', 1) # Sets value based on row name"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "11091a64",
   "metadata": {},
   "outputs": [],
   "source": [
    "small_grid.set_cell_value_by_index('Col 9', 0, 1) # Sets value based on row index"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "86cc3529",
   "metadata": {},
   "source": [
    "#### Events and integration with `ipywidgets`"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "ad95bc07",
   "metadata": {},
   "source": [
    "Listen to cell change events"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b07c004e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def cell_changed(e):\n",
    "    row, column, col_index, value = e['row'], e['column'], e['column_index'], e['value']\n",
    "    print(f'The cell at row {row}, column \"{column}\" (index {col_index}), changed to {value}')\n",
    "    \n",
    "small_grid.on_cell_change(cell_changed)\n",
    "small_grid"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "788a94c3",
   "metadata": {},
   "source": [
    "#### An example with the BarRenderer\n",
    "\n",
    "Renders cell values as horizontal bars based on a scale. `ipydatagrid` has two renderers - `TextRenderer`, which is the default one we've seen, and `BarRenderer`, which we will use now."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "bd0c4dd9",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import LinearScale, ColorScale\n",
    "from ipydatagrid import DataGrid, BarRenderer\n",
    "\n",
    "linear_scale = LinearScale(min=0, max=1)\n",
    "color_scale = ColorScale(min=0, max=1)\n",
    "bar_renderer = BarRenderer(\n",
    "    bar_color=color_scale,\n",
    "    bar_value=linear_scale,\n",
    "    bar_horizontal_alignment=\"center\",\n",
    ")\n",
    "\n",
    "\n",
    "rand_grid = DataGrid(rand_df, default_renderer=bar_renderer, base_column_size=76)\n",
    "rand_grid"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "111ca50d",
   "metadata": {},
   "outputs": [],
   "source": [
    "bar_renderer.show_text = False"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "7dbe7d87",
   "metadata": {},
   "outputs": [],
   "source": [
    "from ipywidgets import FloatSlider, link\n",
    "\n",
    "slider = FloatSlider(\n",
    "    description=\"Scale: \", value=linear_scale.max, min=0, max=0.99, step=0.01\n",
    ")\n",
    "link((color_scale, \"min\"), (slider, \"value\"))\n",
    "link((linear_scale, \"min\"), (slider, \"value\"))\n",
    "\n",
    "slider"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "fb04a1c9",
   "metadata": {},
   "outputs": [],
   "source": [
    "color_scale.min"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4d2fec51",
   "metadata": {},
   "source": [
    "#### Integration with `bqplot`\n",
    "\n",
    "We have a DataGrid with time series of 10 different stock prices. Each time we click on a stock price column, we want to plot the time series of that stock price in a line chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6fd254e9",
   "metadata": {},
   "outputs": [],
   "source": [
    "stock_df.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e20d25bf",
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import LinearScale, Axis, Figure, Lines, CATEGORY10\n",
    "from ipywidgets import HBox, Layout\n",
    "    \n",
    "# Setting up the data grid\n",
    "stock_grid = DataGrid(stock_df, selection_mode='column')\n",
    "\n",
    "# Creating the bqplot chart objects\n",
    "sc_x = LinearScale()\n",
    "sc_y = LinearScale()\n",
    "line = Lines(x=[], y=[], labels=['Fake stock price'], display_legend=True,\n",
    "                 scales={'x': sc_x, 'y': sc_y})\n",
    "ax_x = Axis(scale=sc_x, label='Index')\n",
    "ax_y = Axis(scale=sc_y, orientation='vertical', label='y-value')\n",
    "fig = Figure(marks=[line], axes=[ax_x, ax_y], title='Line Chart', layout=Layout(flex='1 1 auto', width='100%'))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d7658ea1",
   "metadata": {},
   "outputs": [],
   "source": [
    "def plot_stock(*args):\n",
    "    line.y = stock_grid.selected_cell_values\n",
    "    line.x = range(len(line.y))\n",
    "    column_index = stock_grid.selections[0]['c1']\n",
    "    line.labels = [stock_df.columns[column_index]]\n",
    "    line.colors = [CATEGORY10[np.random.randint(0, len(CATEGORY10)) % len(CATEGORY10)]]\n",
    "    \n",
    "# Event listener for cell click\n",
    "stock_grid.observe(plot_stock, names='selections')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "d8b0f854",
   "metadata": {},
   "outputs": [],
   "source": [
    "HBox(\n",
    "    [stock_grid, fig]\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "widgets-tutorial",
   "language": "python",
   "name": "widgets-tutorial"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.13"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
